// Copyright NVIDIA Corporation 2008 -- Edgar Velazquez-Armendariz <edgarv@nvidia.com>

#ifndef NV_BAKER_TILEDTASK_H
#define NV_BAKER_TILEDTASK_H

#include <nvcore/Array2D.h>

#include <IlmThreadPool.h>
#include <IlmThreadMutex.h>
#include <IlmThreadSemaphore.h>

#include <queue>

namespace nv {


	// Struct to represent a tile of a 2D domain: if the domain is a block matrix, a tile
	// is the (m,n) block. This tile represents the domain [xLo,xHi)x[yLo,yHi)
	struct Tile {

		// Block within the original domain
		uint m;
		uint16 n;

		// Lower (inclusive) and upper (exclusive) bounds for the domain
		uint xLo;
		uint xHi;
		uint yLo;
		uint yHi;


		uint16 rows() const {
			nvDebugCheck(yHi-yLo > 0);
			return (yHi-yLo);
		}
		uint16 columns() const {
			nvDebugCheck(xHi-xLo > 0);
			return (xHi-xLo);
		}

		Index2D getIndex(uint16 row, uint16 column) const {
			return Index2D(row+yLo, column+xLo);
		}
	};

	class TiledTaskGenerator;

	/// Our basic unit of work: a tiled task. This task runs on its own
	/// thread and works on a single piece of the original domain
	class TiledTask : public IlmThread::Task {

	public:
		TiledTask(const Tile & tile, TiledTaskGenerator & generator);

		/// Upon its destruction, the task will notify its generator
		virtual ~TiledTask();

		/// The most important method: it actually executes the task
		virtual void execute() = 0;

	protected:
		
		// The tile representing the scope of this task
		const Tile m_tile;

		// The tile generator which produced the tile
		TiledTaskGenerator & m_generator;

		// Syntactic sugar: just calls the generator method
		Index2D indexGlobal(uint row, uint column);
		Index2D indexGlobal(const Index2D &index);
	};


	/// Generator of tiled tasks: it partitions the 2D domain and generates
	/// a new task for each of the tiles. The class provides facilities for
	/// launching the tasks and wait for their completion
	class TiledTaskGenerator {

		friend class TiledTask;

	private:

		/// Size per dimension of each tile, must be greater than zero
		const uint8 m_tileSize;

		/// Dimensions of the domain
		const uint m_width;
		const uint m_height;

		/// Helper variables created along with the object
		uint m_numTilesX;
		uint m_numTilesY;

		/// An array with all the tiles
		Array2D<Tile> *m_tiles;

		/// Queue which will hold the tiles in the order they finish
		std::queue<Tile> m_finishQueue;

		/// Synchronization for the finish queue
		IlmThread::Mutex m_queueMutex;
		IlmThread::Lock  m_queueLock;
		IlmThread::Semaphore m_queueCountSem;

		// This goes last so that its destructor is the first one called: that destructor
		// waits until all tasks are finished, so the threads can use the finish queue
		// even if the caller is not using the take() method
		IlmThread::TaskGroup m_taskGroup;

	protected:

		/// This method will create a new task for the given tile. Each of those
		/// task runs at the runTasks(...) method.
		virtual TiledTask * newTask(const Tile & tile) = 0;

	public:
		TiledTaskGenerator(uint width, uint height, uint8 tileSize = 32);
		virtual ~TiledTaskGenerator();

		inline const Tile & get(uint row, uint column) const {
			return (*m_tiles)(row, column);
		}

		inline uint numTiles() const {
			return (uint)m_numTilesX * (uint)m_numTilesY;
		}

		inline uint width() const {
			return m_width;
		}

		inline uint height() const {
			return m_height;
		}

		/// This is the most important method: it adds one task per tile and executes them
		/// immediately in the provided thread pool. The tasks are generated in the
		/// newTask(const Tile&) method.
		void runTasks(IlmThread::ThreadPool &pool = IlmThread::ThreadPool::globalThreadPool());

		/// Blocking method which retrieves the next finished tile. The calling
		/// thread will block until a tile has been processed.
		Tile take();

	private:
		// Helper function which transforms local tile coordinate into a global one. Assumes
		// that the tile was generated by this class
		Index2D indexGlobal(const Tile & tile, uint row, uint column) {
			nvDebugCheck(row < tile.rows() && column < tile.columns());
			nvDebugCheck( tile.m*m_tileSize + row < height() );
			nvDebugCheck( tile.n*m_tileSize + column < width() );
			return Index2D(tile.m*m_tileSize + row, tile.n*m_tileSize + column);
		}

	};

	// Syntactic sugar: just calls the generator method
	inline Index2D TiledTask::indexGlobal(uint row, uint column) {
		return m_generator.indexGlobal(m_tile, row, column);
	}
	inline Index2D TiledTask::indexGlobal(const Index2D &index) {
		return m_generator.indexGlobal(m_tile, index.row, index.column);
	}



} // namespace nv


#endif // NV_BAKER_TILEDTASK_H
